package compilador.semantico;

import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import compilador.semantico.TabelaSemantica.TiposPrimitivo;

public class XMIParser {

	private static final String MDXML_FILE_SUFFIX = "mdxml";
	private final static String XMI_TYPE = "xmi:type";
	private final static String XMI_ID = "xmi:id";
	private final static String CLASS_TYPE = "uml:Class";
	private final static String ASSOCIATION_CLASS_TYPE = "uml:AssociationClass";
	private final static String ENUM_TYPE = "uml:Enumeration";
	private final static String CLASS_TAG = "ownedMember";
	private final static String TYPE_TAG = "type";
	private final static String ATT_TAG = "ownedAttribute";
	private final static String OPERATION_TAG = "ownedOperation";
	private final static String PARAMETER_TAG = "ownedParameter";
	private final static String UPPERVALUE_TAG = "upperValue";
	private final static String GENERALIZATION_TAG = "generalization";
	private final static String GENERALIZATION_ATT = "general";
	private final static String VALUE_ATT = "value";
	private final static String NAME_ATT = "name";
	private final static String HREF_ATT = "href";
	private final static String TYPE_ATT = "type";
	private final static String DIRECTION_ATT = "direction";
	private final static String ASSOCIATION_ATT = "association";
	private static final String RETURN = "return";

	private TabelaSemantica tabelaSemantica = TabelaSemantica.getInstance();
	private Map<String, Element> elements = new HashMap<String, Element>();

	public void parse(String fileName) {
		try {
			Document doc = createDocument(getMDXMLInputStream(fileName));
			processDoc(doc);
			loadTypes();
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			System.err.println("Erro lendo arquivo xmi : " + e.getMessage());
		} catch (IOException e) {
			System.err.println("Erro de entrada e saida: " + e.getMessage());
		}
	}

	private InputStream getMDXMLInputStream(String filename) throws IOException {
		ZipFile zip = new ZipFile(filename);
		InputStream in = null;
		Enumeration<? extends ZipEntry> entries = zip.entries();
		while (entries.hasMoreElements()) {
			ZipEntry entry = entries.nextElement();
			if (!entry.isDirectory() && entry.getName().endsWith(MDXML_FILE_SUFFIX)) {
				in = zip.getInputStream(entry);
				break;
			}
		}
		if (in == null) {
			throw new IOException("Impossivel localizar o arquivo " + MDXML_FILE_SUFFIX);
		}
		return in;
	}

	private Document createDocument(InputStream in) throws SAXException, ParserConfigurationException, IOException {
		DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
		return builder.parse(in);
	}

	private void processDoc(Document doc) {
		NodeList nodes = doc.getElementsByTagName(CLASS_TAG);
		for (int i = 0; i < nodes.getLength(); i++) {
			Element node = (Element) nodes.item(i);
			String type = node.getAttribute(XMI_TYPE);
			if (type.equals(CLASS_TYPE) || type.equals(ENUM_TYPE) || type.equals(ASSOCIATION_CLASS_TYPE)) {
				String id = getElementId(node);
				elements.remove(id);
				elements.put(id, node);
			}
		}
	}

	private void loadTypes() {
		List<Element> subTypes = new LinkedList<Element>();
		for (String key : elements.keySet()) {
			Element el = elements.get(key);
			// ignora classes sem nome
			if (el.hasAttribute(NAME_ATT)) {
				Tipo tipo = null;
				String name = getElementName(el);
				NodeList nodes = el.getElementsByTagName(GENERALIZATION_TAG);
				if (nodes.getLength() != 0) {
					// se for um subtipo deixa pra depois.
					// eh preciso carregar os supertipos primeiro
					subTypes.add(el);
					continue;
				}			
				tipo = new Tipo(name);
				loadAttributes(tipo, el);
				loadMethods(tipo, el);
				tabelaSemantica.addTipo(tipo);
			}
			
			
		} 
		// carrega os subtipos
		for(Element el : subTypes) {
			String name = getElementName(el);
			NodeList nodes = el.getElementsByTagName(GENERALIZATION_TAG);
			Element generalization = (Element) nodes.item(0);
			String superTipo = getGeneralName(generalization);
			Tipo tipo = new Tipo(name, (Tipo) tabelaSemantica.getTipo(superTipo));
			loadAttributes(tipo, el);
			loadMethods(tipo, el);
			tabelaSemantica.addTipo(tipo);
		}
	}



	private void loadAttributes(Tipo tipo, Element el) {
		NodeList nodes = el.getElementsByTagName(ATT_TAG);
		for (int i = 0; i < nodes.getLength(); i++) {
			Element el1 = (Element) nodes.item(i);
			String type = getTypeName(el1);
			String name = getAttName(el1);
			boolean colecao = checkColecao(el1);
			tipo.addAtributo(new Atributo(name, type, colecao));
		}
	}

	private void loadMethods(Tipo tipo, Element el) {
		NodeList nodes = el.getElementsByTagName(OPERATION_TAG);
		for (int i = 0; i < nodes.getLength(); i++) {
			Element el1 = (Element) nodes.item(i);
			String name = getElementName(el1);
			String retorno = getRetorno(el1);
			Metodo met = new Metodo(name, retorno);
			getParametros(met, el1);
			tipo.addMetodo(met);
		}
	}

	private boolean checkColecao(Element el) {
		if (el.hasAttribute(ASSOCIATION_ATT)) {
			NodeList nodes = el.getElementsByTagName(UPPERVALUE_TAG);
			if (nodes.getLength() != 0) {
				Element upper = (Element) nodes.item(0);
				return -1 == Integer.parseInt(upper.getAttribute(VALUE_ATT));
			}
		}
		return false;
	}

	private void getParametros(Metodo met, Element el) {
		NodeList nodes = el.getElementsByTagName(PARAMETER_TAG);
		for (int i = 0; i < nodes.getLength(); i++) {
			Element el1 = (Element) nodes.item(i);
			String direction = el1.getAttribute(DIRECTION_ATT);
			if(!direction.equals(RETURN)) {
				String type = getTypeName(el1);
				String name = getElementName(el1);
				Atributo att = new Atributo(name, type);
				met.addParametro(att);
			}
		}
	}

	private String getRetorno(Element el) {
		NodeList nodes = el.getElementsByTagName(PARAMETER_TAG);
		for (int i = 0; i < nodes.getLength(); i++) {
			Element el1 = (Element) nodes.item(i);
			String direction = el1.getAttribute(DIRECTION_ATT);
			if (direction.equals(RETURN)) {
				return getTypeName(el1);
			}
		}
		return TiposPrimitivo.Void.name();
	}

	private String getAttName(Element el) {
		String name = el.getAttribute(NAME_ATT);
		if (name.equals("")) {
			name = getTypeName(el).toLowerCase() + "s";
		}
		return name;
	}

	private String getGeneralName(Element generalization) {
		String type = generalization.getAttribute(GENERALIZATION_ATT);
		return getElementName(elements.get(type));
	}
	
	private String getTypeName(Element el) {
		String type = el.getAttribute(TYPE_ATT);
		if (type.equals("")) {
			NodeList nodes = el.getElementsByTagName(TYPE_TAG);
			if (nodes.getLength() != 0) {
				Element typeEl = (Element) nodes.item(0);
				String href = typeEl.getAttribute(HREF_ATT);
				return href.split("#")[1];
			}
		} else {
			return getElementName(elements.get(type));
		}
		return null;
	}

	private String getElementName(Element el) {
		String name = el.getAttribute(NAME_ATT);
		return (!name.equals("")) ? name : getElementId(el);
	}

	private String getElementId(Element el) {
		return el.getAttribute(XMI_ID);
	}
}